/* 
 * File:   parallelife2.cpp
 * Author: jacob
 *
 * Created on June 8, 2010, 2:49 PM
 */


#include <stdlib.h>
#include <stdio.h>
#include <mpi.h>
#include <string>
#include <string.h>
#include <iostream>
#include <fstream>
#include <set>


using namespace std;

// Declaracion de las variables globales
bool** actual;
bool** tmp;
bool** nueva;
int total_hilos;
int total_iteraciones;
int total_filas;
int iTam;
int jTam;
int total_columnas;
int Lapareamiento;
int Lsoledad;
int Lhacinamiento;
int Nsnapshot;
string PrefijoFoto;
string nombre_estado_inicial;
string EstadoInicial;
int total_nodos;
int nodo_id;
int fila_global_inicio;
int fila_global_fin;
pthread_barrier_t barr;
pthread_barrier_t barrMain;

/*
 * Cuenta los vecinos dados una coordenada i,j.
 */
int contarVecinos(int il, int jl) {
    int vecinos = 0;
    for (int i = -1; i < 2; i++) {
        for (int j = -1; j < 2; j++) {
            if (i == 0 && j == 0)
                continue;
            if (actual[il + i][(jl + j < 0 ? jTam - 1 : jl + j) % jTam])
                vecinos++;
        }
    }
    return vecinos;
}

/*
 * Se Verifica si en el punto i j de la matriz, la celula vive o muere
 */
void verificacion(int i, int j) {
    int vecinos = contarVecinos(i, j);

    // cout << "I: " << i << " j: " << j << " tiene " << vecinos << endl;
    if (actual[i][j]) {

        if (vecinos < Lsoledad || vecinos > Lhacinamiento)
            nueva[i][j] = false;
        else
            nueva[i][j] = true;

    } else {

        if (vecinos == Lapareamiento)
            nueva[i][j] = true;
    }
}

/*
 *  Carga los posibles, candidatos al conjunto
 */
set< pair<int, int > > *iniciarActCel(int i1, int i2, int h) {
    set<pair<int, int> > *retorno = new set<pair<int, int> >;

    if (i1 >= iTam + 1) {
        cout << "Error al segmentar matriz" << endl;
        return NULL;
    }

    for (int i = i1; i <= i2; i++) {
        for (int j = 0; j < jTam; j++) {

            if (actual[i][j]) {
                retorno->insert(make_pair(i, j));
                retorno->insert(make_pair(i, (j + 1) % jTam));
                retorno->insert(make_pair(i, (j - 1 < 0 ? jTam - 1 : j - 1)));

                if (i - 1 >= i1) {
                    retorno->insert(make_pair(i - 1, j));
                    retorno->insert(make_pair(i - 1, (j + 1) % jTam));
                    retorno->insert(make_pair(i - 1, (j - 1 < 0 ? jTam - 1 : j - 1)));
                }

                if (i + 1 <= i2) {
                    retorno->insert(make_pair(i + 1, j));
                    retorno->insert(make_pair(i + 1, (j + 1) % jTam));
                    retorno->insert(make_pair(i + 1, (j - 1 < 0 ? jTam - 1 : j - 1)));
                }

            }

        }
    }

    for (int j = 0; j < jTam; j++) {

        if (actual[i1 - 1][j]) {

            retorno->insert(make_pair(i1, j));
            retorno->insert(make_pair(i1, (j + 1) % jTam));
            retorno->insert(make_pair(i1, (j - 1 < 0 ? jTam - 1 : j - 1)));
        }


        if (actual[i2 + 1][j]) {

            retorno->insert(make_pair(i2, j));
            retorno->insert(make_pair(i2, (j + 1) % jTam));
            retorno->insert(make_pair(i2, (j - 1 < 0 ? jTam - 1 : j - 1)));
        }

    }

    return retorno;

}

/*
 * Itera sobre los posibles vecinos y verifica si puede
 * agregarlo a la nueva matriz
 */
void corridaMatriz(set<pair<int, int> > *posibles) {

    set<pair<int, int> >::iterator it;


    for (it = posibles->begin(); it != posibles->end(); it++) {
        verificacion(it->first, it->second);

    }
}

/*
 * Codigo para los hilos de cada uno de los nodos
 */
void * codigoHilo(void *thread_id) {
    int *id_ptr, tarea_id;
    set< pair<int, int> > *candidatos = new set<pair<int, int> >;
    id_ptr = (int *) thread_id;

    tarea_id = *id_ptr;
    int iIni = id_ptr[0];
    int iFinal = id_ptr[1];
    int h = id_ptr[2];

    while (true) {
        if (iFinal != 0) {
            candidatos = iniciarActCel(iIni, iFinal, h);
            //Recorro los candidatos
            corridaMatriz(candidatos);
        }


        int rc = pthread_barrier_wait(&barr);
        if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) {
            printf("Tumbe el muro.\n");
            exit(-1);
        }

        int rc1 = pthread_barrier_wait(&barrMain);
        if (rc1 != 0 && rc1 != PTHREAD_BARRIER_SERIAL_THREAD) {
            printf("Tumbe el muro.\n");
            exit(-1);
        }

        //      cout << "Encontre Todos los hilos" << endl;
        if (iFinal != 0) {

            free(candidatos);

        }



    }

    pthread_exit(NULL);

}

/*
 * Carga lee los argumentos y los copia en las variables globales.
 */
int leerArgumentos(int argc, char** argv) {

    for (int i = 1; i < argc; i++) {
        int j = i + 1;

        if (!strcmp(argv[i], "-n"))
            total_iteraciones = atoi(argv[j]);

        else if (!strcmp(argv[i], "-w"))
            total_columnas = atoi(argv[j]);

        else if (!strcmp(argv[i], "-h"))
            total_filas = atoi(argv[j]);

        else if (!strcmp(argv[i], "-la"))
            Lapareamiento = atoi(argv[j]);

        else if (!strcmp(argv[i], "-ls"))
            Lsoledad = atoi(argv[j]);

        else if (!strcmp(argv[i], "-lh"))
            Lhacinamiento = atoi(argv[j]);

        else if (!strcmp(argv[i], "-o"))
            PrefijoFoto.assign(argv[j]);

        else if (!strcmp(argv[i], "-i"))
            nombre_estado_inicial.assign(argv[j]);

        else if (!strcmp(argv[i], "-s"))
            Nsnapshot = atoi(argv[j]);

        else if (!strcmp(argv[i], "-m"))
            total_hilos = atoi(argv[j]);

        else
            cout << "Este parametro no es conocido " << argv[i] << endl;


        i = j;

    }
    return 1;
}

/*
 * Reserva el espacio para las matrices actual y nueva.
 */
void inicializarMatrizes() {

    //Reserva de espacio para las matrices
    actual = (bool**) calloc(iTam + 2, sizeof (bool*));
    nueva = (bool**) calloc(iTam + 2, sizeof (bool*));

    if (actual == NULL || nueva == NULL) {
        cout << "Error en la reserva de memoria" << endl;
        exit(-1);
    }
    for (int i = 0; i < iTam + 2; i++) {

        actual[i] = (bool*) calloc(jTam, sizeof (bool));
        nueva[i] = (bool*) calloc(jTam, sizeof (bool));
        if (actual == NULL || nueva == NULL) {
            cout << "Error en la reserva de memoria" << endl;
            exit(-1);
        }

    }

}

/*
 * determino las filas globales que le asigno a este nodo
 */
void asignarFilasGlobales() {

    int cantidad_filas_asignar;
    int total_filas_restantes = total_filas;
    int valor = 0;
    int nodos = total_nodos;

    //Determina que valor de fila inicial y final final le pertenece al nodo actual
    for (int nodos_restantes = 0; nodos_restantes < total_nodos; nodos_restantes++) {

        cantidad_filas_asignar = total_filas_restantes / nodos;
        nodos--;
        total_filas_restantes = total_filas_restantes - cantidad_filas_asignar;
        valor += cantidad_filas_asignar;

        fila_global_inicio = valor - cantidad_filas_asignar;
        fila_global_fin = valor - 1;

        //Cuando llega al nodo actual se detiene
        if (nodos_restantes == nodo_id) {
            break;
        }
    }


    //Asigno el tamaanyo de i y j real para el nodo en proceso, Y creo las matrices.
    iTam = fila_global_fin - fila_global_inicio + 1;
    jTam = total_columnas;
    inicializarMatrizes();
}

/*
 * Imprime la matriz en la pantalla.
 */
void snapshot() {

    sleep(nodo_id);
    for (int i = 1; i < iTam + 1; i++) {
        for (int j = 0; j < jTam; j++) {
            cout << actual[i][j] << " ";
        }
        cout << endl;
    }
}

/*
 * Funcion que dado un arreglo de bool, lo copia a la fila 0 y
 * ultima de la matriz "actual"
 */
void copiarArreglo(bool* para_copiar, int fila_a_copiar) {

    for (int i = 0; i < jTam; i++) {
        actual[fila_a_copiar][i] = para_copiar[i];
    }

}

//Funcion que realiza el intercambio de la informacion.

void pasarBordes() {
    //Enviando las  filas bordes a los demas nodos

    //El primer nodo, o nodo 0, no debe enviar su primera fila  a nadie.
    if (nodo_id != 0)
        MPI::COMM_WORLD.Isend(actual[1], jTam, MPI::INT, nodo_id - 1, 1);
    else
        MPI::COMM_WORLD.Isend(actual[1], jTam, MPI::INT, total_nodos - 1, 1);



    //La ultima fila recibe de la primera.
    if ((nodo_id != total_nodos - 1) && total_nodos > 1) {
        bool men[jTam];
        MPI::COMM_WORLD.Recv(&men, jTam, MPI::INT, nodo_id + 1, 1);
        //copiar men a la ultima fila de actual
        copiarArreglo(men, iTam + 1);
    } else {
        bool men[jTam];
        MPI::COMM_WORLD.Recv(&men, jTam, MPI::INT, 0, 1);
        //copiar men a la ultima fila de actual
        copiarArreglo(men, iTam + 1);
    }

    //El ultimo nodo, o nodo total_nodos, no debe enviar su ultima fila a nadie.
    if (nodo_id != total_nodos - 1)
        MPI::COMM_WORLD.Isend(actual[iTam], jTam, MPI::INT, nodo_id + 1, 1);
    else
        MPI::COMM_WORLD.Isend(actual[iTam], jTam, MPI::INT, 0, 1);

    //La primera fila, no recibe la ultima fila de nadie.
    if (nodo_id != 0) {
        bool men[jTam];
        MPI::COMM_WORLD.Recv(&men, jTam, MPI::INT, nodo_id - 1, 1);
        //copiar men a la primera fila de actual
        copiarArreglo(men, 0);
    } else {
        bool men[jTam];
        MPI::COMM_WORLD.Recv(&men, jTam, MPI::INT, total_nodos - 1, 1);
        //copiar men a la primera fila de actual
        copiarArreglo(men, 0);
    }

}

/*
 * Es el codigo que va a correr el nodo una vez a sido cargado los datos
 * iniciales.
 */
int codigoNodos() {
    int rc, rc1, n, it;
    MPI::Request request;
    MPI::Status status;
    n = 1;
    it = 1;

    pasarBordes();


    // Inicializo las barreras
    if (pthread_barrier_init(&barr, NULL, total_hilos + 1)) {
        printf("No ahi bloques para construir el muro\n");
        return -1;
    }

    // Inicializo las barreras
    if (pthread_barrier_init(&barrMain, NULL, total_hilos + 1)) {
        printf("No ahi bloques para construir el muro\n");
        return -1;
    }

    //Creación de hilos
    pthread_t threads[total_hilos];
    pthread_attr_t mis_atr;
    int tarea_ids[total_hilos][3];
    int t;

    if (pthread_attr_init(&mis_atr)) {
        perror("No se pudo inicializar atributos");
        exit(1);
    }

    pthread_attr_setscope(&mis_atr, PTHREAD_SCOPE_PROCESS);

    int tmpI = iTam;
    int h = total_hilos;
    int valor = 1;

    for (t = 0; t < total_hilos; t++) {

        int tam = tmpI / h;
        h--;
        tmpI = tmpI - tam;
        valor += tam;

        tarea_ids[t][0] = valor - tam;
        tarea_ids[t][1] = valor - 1;
        tarea_ids[t][2] = t;

        //cout << tarea_ids[t][0] << " " << tarea_ids[t][1] << endl;

        pthread_create(&threads[t], NULL, codigoHilo, (void *) tarea_ids[t]);

    }


    for (int j = 0; j < total_iteraciones; j++) {

        //Aqui tiene que esperar
        // Synchronization point
        rc = pthread_barrier_wait(&barr);
        if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) {
            printf("Tumbe el muro.\n");
            exit(-1);
        }

        //Cambia la matriz y crea de nuevo la nueva
        tmp = nueva;
        if ((nueva = (bool**) calloc(iTam + 2, sizeof (bool*))) == NULL) {
            cout << "Error en la reserva de memoria" << endl;
            exit(-1);
        }
        for (int i = 0; i < iTam + 2; i++) {

            if (((nueva[i] = (bool*) calloc(jTam, sizeof (bool))) == NULL)) {
                cout << "Error en la reserva de memoria" << endl;
                exit(-1);
            }
            free(actual[i]);
        }
        free(actual);
        actual = tmp;

        //     cout << nodo_id <<":" << endl;
        //   snapshot();

        //
        //        if (n == Nsnapshot) {
        //            cout ;
        //        }


        pasarBordes();
        snapshot();

        if(n == Nsnapshot){
            //snapshot();
            //sleep(1);
            if(nodo_id==0){
            char  buffer [50];
            string buf= buffer;
            sprintf(buffer,"%d",it);
            char * nombre  ;
            nombre = (char *)(PrefijoFoto+buf).c_str() ;
            ofstream salida( nombre  , ios::out);

                // escribir en archivo su matriz
                for(int i = 1 ; i <= iTam ; i++){
                    for (int j = 0 ; j < jTam ; j++){
                        if(actual[i][j])
                            salida << i-1 << ":" << j << endl ;
                    }
                }

                for (int i = 1 ; i < total_nodos ; i++){
                    int msj[2];
                    while(true){
                    //recibir fila de nodo i
                        MPI::COMM_WORLD.Recv(&msj, 2, MPI::INT, i, 1);

                        if(msj[0]==-1)
                            break;
                        
                        //escribir en archivo
                        salida << msj[0] << ":" << msj[1] << endl ;   
                        
                     }
                }
            }
            else{
                //enviar fila
                int msj[2] ;
                for (int i=1 ; i < iTam ; i ++ )
                    for(int j=0 ; j < jTam; j++)
                        if(actual[i][j]){
                            msj[0] = i + fila_global_inicio;
                            msj[1] = j ;
                            MPI::COMM_WORLD.Isend(msj, 2, MPI::INT, 0, 1);
                        }
                msj[0] = -1;
                MPI::COMM_WORLD.Isend(msj, 2, MPI::INT, 0, 1);

            }


            n = 0;
        }
        it++;
        n++;

        pasarBordes();
       





        
        rc1 = pthread_barrier_wait(&barrMain);
        if (rc1 != 0 && rc1 != PTHREAD_BARRIER_SERIAL_THREAD) {
            printf("Tumbe el muro.\n");
            exit(-1);
        }



    }

    for (int i = 0; i < total_hilos; i++) {
        pthread_cancel(threads[i]);

    }

    return 1;

}

/*
 * determino las filas globales que le asigno a este nodo
 */
void asignarFilasGlobales(int** m) {

    fila_global_fin = -1;

    int total_filas_restantes = total_filas;
    int cantidad_filas_asignar;
    int valor = 0;
    int nodos = total_nodos;

    //Determina que valor de fila inicial y final final le pertenece al nodo actual
    for (int nodos_restantes = 0; nodos_restantes < total_nodos; nodos_restantes++) {

        cantidad_filas_asignar = total_filas_restantes / nodos;
        nodos--;
        total_filas_restantes = total_filas_restantes - cantidad_filas_asignar;
        valor += cantidad_filas_asignar;

        m[nodos_restantes][0] = valor - cantidad_filas_asignar;
        m[nodos_restantes][1] = valor - 1;


    }


}

/*
 *Carga el estado inicial de el archivo que se pasa como parametro en el main.
 */
int cargarEstadoInicial() {
    ifstream in;
    int i, j;
    char c;
    int t = total_nodos;
    int filasColumnas[t][2];
    int msj[2];

    //Llenado de filas

    fila_global_fin = -1;

    int total_filas_restantes = total_filas;
    int cantidad_filas_asignar;
    int valor = 0;
    int nodos = total_nodos;

    //Determina que valor de fila inicial y final final le pertenece al nodo actual
    for (int nodos_restantes = 0; nodos_restantes < total_nodos; nodos_restantes++) {

        cantidad_filas_asignar = total_filas_restantes / nodos;
        nodos--;
        total_filas_restantes = total_filas_restantes - cantidad_filas_asignar;
        valor += cantidad_filas_asignar;

        filasColumnas[nodos_restantes][0] = valor - cantidad_filas_asignar;
        filasColumnas[nodos_restantes][1] = valor - 1;


    }//Fin de llenado de fila


    //Se lee el archivo de estado inicial.
    in.open(nombre_estado_inicial.c_str());
    while (!in.eof()) {
        in >> i >> c >> j;
        //Se verifica el nodo al que se le enviará la coordenada.
        for (int k = 0; k < total_nodos; k++) {
            if (filasColumnas[k][0] <= i && filasColumnas[k][1] >= i) {
                if (k == 0) {
                    // cout << "soy el proceso " << nodo_id << " agrego los puntos " << i << " " << j << endl;
                    actual[i - fila_global_inicio + 1][j] = true;
                } else {
                    msj[0] = i;
                    msj[1] = j;
                    MPI::COMM_WORLD.Isend(&msj, 2, MPI::INT, k, 1);
                }
            }

        }



        //Se le envia el mensaje al nodo rank = i mod #numerodenodos

    }

    msj[0] = -1;
    msj[1] = -1;
    //Envia el mensaje a los demas nodos que ya ha pasado todas las coordenadas.
    for (int k = 1; k < total_nodos; k++)
        MPI::COMM_WORLD.Isend(&msj, 2, MPI::INT, k, 1);

    return 1;
}

int main(int argc, char** argv) {

    int msj[2];

    //Inicializo MPI, cargo el id del nodo y la cantidad de nodos que ahi
    MPI::Init(argc, argv);
    nodo_id = MPI::COMM_WORLD.Get_rank();
    total_nodos = MPI::COMM_WORLD.Get_size();

    //Cargo los argumentos
    if (argc != 21) {
        cout << "No tienes los argumentos correctos" << endl;
        exit(-1);
    }

    leerArgumentos(argc, argv);

    //Cargo las filas pertenecientes al nodo
    //cout << "Proceso " << nodo_id << " de " << total_nodos << endl;
    //sleep(nodo_id);
    asignarFilasGlobales();
    //cout << "Inicio: " << fila_global_inicio << " Fin: " << fila_global_fin << endl;

    //Cargo el estado inicial
    if (nodo_id == 0) {
        cargarEstadoInicial();
    } else {
        while (true) {
            MPI::COMM_WORLD.Recv(&msj, 2, MPI::INT, 0, 1);
            if (msj[0] == -1)
                break;
            actual[msj[0] - fila_global_inicio + 1][msj[1]] = true;
        }
    }
    
    codigoNodos();
    MPI::Finalize();

    return (EXIT_SUCCESS);
}
